<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace Hyperfx\Framework\Tracer\Aspect;

use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\GrpcClient\BaseClient;
use Hyperf\Rpc\Context;
use Hyperf\Tracer\SpanStarter;
use Hyperf\Tracer\SpanTagManager;
use Hyperf\Tracer\SwitchManager;
use OpenTracing\Tracer;
use Psr\Container\ContainerInterface;
use const OpenTracing\Formats\TEXT_MAP;

class GrpcAspect implements AroundInterface
{
    use SpanStarter;

    public $classes = [
        BaseClient::class . '::_simpleRequest',
    ];

    /**
     * @var ContainerInterface
     */
    private $container;

    /**
     * @var Tracer
     */
    private $tracer;

    /**
     * @var SwitchManager
     */
    private $switchManager;

    /**
     * @var SpanTagManager
     */
    private $spanTagManager;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
        $this->tracer = $container->get(Tracer::class);
        $this->switchManager = $container->get(SwitchManager::class);
        $this->spanTagManager = $container->get(SpanTagManager::class);
    }

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        if ($proceedingJoinPoint->methodName === '_simpleRequest') {

            $arguments = $proceedingJoinPoint->arguments;
            $path = $arguments['keys']['method'] ?? '';
            $request = $arguments['keys']['argument'] ?? null;
            $metadata = $arguments['keys']['metadata'] ?? [];
            $options = $arguments['keys']['options'] ?? [];

            $key = "gRPC send [{$path}]";
            $span = $this->startSpan($key);

            if ($this->spanTagManager->has('grpc', 'path')) {
                $span->setTag($this->spanTagManager->get('grpc', 'path'), $path);
            }

            if ($this->spanTagManager->has('grpc', 'request') && !empty($request)) {
                $span->setTag($this->spanTagManager->get('grpc', 'request'), $request->serializeToJsonString());
            }

            if ($this->spanTagManager->has('grpc', 'metadata') && !empty($metadata)) {
                foreach ($metadata as $key => $value) {
                    $span->setTag($this->spanTagManager->get('request', 'metadata') . '.' . $key, is_array($value) ? implode(', ', $value) : $value);
                }
            }

            if ($this->spanTagManager->has('grpc', 'option') && !empty($options)) {
                foreach ($options as $key => $value) {
                    $span->setTag($this->spanTagManager->get('request', 'option') . '.' . $key, is_array($value) ? implode(', ', $value) : $value);
                }
            }

            $appendHeaders = [];
            // Injects the context into the wire
            $this->tracer->inject(
                $span->getContext(),
                TEXT_MAP,
                $appendHeaders
            );
            $proceedingJoinPoint->arguments['keys']['metadata'] = $proceedingJoinPoint->arguments['keys']['metadata'] + $appendHeaders;

            try {
                $result = $proceedingJoinPoint->process();
                $status = $result[1];
                if ($status > 0) {
                    $span->setTag('error', true);
                    if ($this->spanTagManager->has('grpc', 'status_code')) {
                        $span->setTag($this->spanTagManager->get('grpc', 'status_code'), $status);
                    }
                    if ($this->spanTagManager->has('grpc', 'status_message')) {
                        $message = $result[0];
                        $span->setTag($this->spanTagManager->get('grpc', 'status_message'), $message);
                    }
                }
            } catch (\Throwable $e) {
                $span->setTag('error', true);
                $span->log(['message', $e->getMessage(), 'code' => $e->getCode(), 'stacktrace' => $e->getTraceAsString()]);
                throw $e;
            } finally {
                $span->finish();
            }
            return $result;
        }

        return $proceedingJoinPoint->process();
    }
}
